package qj.util.tcp;

import java.io.IOException;
import java.net.Socket;

import qj.util.IOUtil;
import qj.util.ThreadUtil4;
import qj.util.funct.F1;
import qj.util.funct.P0;
import qj.util.funct.P1;

public class TextSocket {
	private static final char DELIMITER = '~';

	private final Socket sk;

	private F1<String, String> textReceiver;
	private boolean alive = true;
	private char delimiter = DELIMITER;
	
//	/** Logger **/
//	private static final Log log = LogFactory.getLog(TextSocket.class);

	private final P1<IOException> SELF_CLOSE_EXC_HANDLER = new P1<IOException>() {public void e(IOException obj) {
//		obj.printStackTrace();
		close();
	}
    };
	protected P1<IOException> readExcHandler = SELF_CLOSE_EXC_HANDLER;
	protected P1<IOException> writeExcHandler = SELF_CLOSE_EXC_HANDLER;

	P1<String> reader = new P1<String>() {public void e(String msg) {
		for (int i = 0; textReceiver==null && i < 10; i++) {
			ThreadUtil4.sleep(200);
		}
//		if (textReceiver == null) {
//			log.warn("Message \"" + msg + "\" received but no handler registered");
//		}
//		log.debug("Received " + msg);
		String evaluate = textReceiver.e(msg);
		if (evaluate!=null) {
			write(evaluate);
		}
	}
    };

	private P0 closeListener;
	
	/**
	 * Auto create a thread to listen to messages
	 * @param sk
	 */
	public TextSocket(final Socket sk) {
		this.sk = sk;
		
		try {
			// When reading, lock writing
			IOUtil.asyncReadUntil(sk.getInputStream(), delimiter, reader, readExcHandler, writeLock);
		} catch (IOException e) {
			readExcHandler.e(e);
		}
	}
	

	public synchronized String read(String input, int timeout) {
//		log.debug("Reading");
		F1<String, String> actualTextReceiver;
		String text;

		// CLASSES
		class BufferTextReceiver implements F1<String, String> {
			private String text;

			public synchronized String e(String text) {
				this.text = text;
				ThreadUtil4.notifyAll(this);
				return null;
			}

			public String getText() {
				return text;
			}
        }
		BufferTextReceiver tempTextReceiver = new BufferTextReceiver();
		// Synchronize so message is not sent before ready to read
		synchronized (tempTextReceiver) {
			tempTextReceiver.text = null;
			actualTextReceiver = textReceiver;
			textReceiver = tempTextReceiver;
			if (input != null) {
				write(input);
			}
			ThreadUtil4.wait(tempTextReceiver, timeout);
			text = tempTextReceiver.getText();
		}
		textReceiver = actualTextReceiver;
//		log.debug("Read done");
		return text;
	}

	public synchronized String read(String input) {
		return read(input, 0);
	}

	private Object writeLock = new Object();
	public synchronized void write(String input) {
		synchronized (writeLock) {
			try {
//				log.debug("Sending " + input);
				IOUtil.write(input + delimiter, sk.getOutputStream());
			} catch (IOException e) {
				writeExcHandler.e(e);
			}
//			log.debug("Send done");
		}
	}

	public synchronized void setTextReceiver(F1<String, String> textReceiver) {
		this.textReceiver = textReceiver;
	}

	public synchronized void close() {
		if (alive) {
			alive = false;
			try {
				sk.close();
			} catch (IOException e) {
			}

			if (closeListener!=null) {
				closeListener.e();
				closeListener = null;
			}
		}
	}

	public boolean isAlive() {
		return alive;
	}

	public void setCloseListener(P0 closeListener) {
		this.closeListener = closeListener;
	}


	public char getDelimiter() {
		return delimiter;
	}


	public void setDelimiter(char delimiter) {
		this.delimiter = delimiter;
	}
}
